Skip to content

Conversation

@haewiful
Copy link
Contributor

@haewiful haewiful commented Jan 8, 2026

This pull requests updates the jdk-javac-plugin to parse the nested annotations in external libraries and creates JSON files according to it. For the nested annotation information, it uses NestedAnnotationInfo class. It is the first step from #1410.

The added unit test for this is nestedAnnotationsForMethods().

Summary by CodeRabbit

  • New Features

    • Detects and records nested JSpecify nullability annotations inside generics, arrays, and wildcard bounds for return types and parameters; introduces a structured representation for nested annotation metadata.
  • Serialization / Compatibility

    • JSON output now includes nested annotation mappings; JSON parsing updated to correctly reconstruct Guava ImmutableList contents.
  • Tests

    • Added unit tests validating nested-annotation capture and end-to-end serialization for methods.
  • Chores

    • Build configuration updated to include required library dependency.

✏️ Tip: You can customize this high-level summary in your review settings.

@msridhar msridhar marked this pull request as ready for review January 8, 2026 18:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Walkthrough

Adds detection, modeling, and serialization of nested JSpecify nullability annotations: introduces a public NestedAnnotationInfo record (with TypePathEntry and enums); implements CreateNestedAnnotationInfoVisitor to collect nested Nullable/NonNull annotations in class type arguments, array elements, and wildcard bounds; extends MethodInfo to include a Map<Integer, Set> holding return and per-parameter nested annotations and updates NullnessAnnotationSerializer to populate that map; adds Gson ImmutableList adapters for JSON parsing in tests and astubx-generator; and adds a Guava implementation dependency to the plugin build.

Possibly related issues

  • uber/NullAway issue 1410 — Implements parsing and JSON emission of nested JSpecify annotations, which matches the issue objective.

Possibly related PRs

  • uber/NullAway PR 1316 — Also modifies NullnessAnnotationSerializer to inspect and handle nested JSpecify annotations; overlaps serializer logic.
  • uber/NullAway PR 1243 — Adds ImmutableList/Gson adapter and adjusts JSON parsing in astubx-related code, directly related to the astubx-generator and test deserialization changes.
  • uber/NullAway PR 1408 — Modifies NullnessAnnotationSerializer in the same file and touches MethodInfo/serialization structures changed here.

Suggested reviewers

  • yuxincs
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Update jdk-javac-plugin to contain nested annotations' directly and clearly describes the main change: adding nested annotation support to the jdk-javac-plugin. It aligns with the core objective stated in the PR summary.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e879332 and c558007.

📒 Files selected for processing (2)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
🔇 Additional comments (8)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (4)

6-30: LGTM: Import additions.

The new imports support the ImmutableList adapter (JsonDeserializer, JsonArray, JsonElement, ParameterizedType, Type), nested annotation types (NestedAnnotationInfo, TypePathEntry, Annotation), and Set for the nested annotation map values.


119-120: LGTM: Updated test expectations with empty nested annotation maps.

The Map.of() additions align with the updated MethodInfo record signature that now includes nestedAnnotationsList.


395-507: Comprehensive nested annotation test coverage.

This test method thoroughly covers:

  • Parameter nested annotations (@NonNull E in type argument)
  • Return type nested annotations (List<@Nullable String>)
  • Array element annotations (@Nullable String [])
  • Arrays of parameterized types (List<@NonNull String>[])
  • Wildcard upper bounds (? extends @nullable String)
  • Nested type arguments without array elements (List<List<String @nullable []>>)

The test cases address the scenarios requested in past review comments, including the List<List<String @nullable []>> case that should have two type arguments but no array elements in the type path.


355-364: No action required—the wildcard bound index convention is correct.

The test at line 364 correctly uses WILDCARD_BOUND index 1 for the super bound (? super @nullable Integer). This aligns with the documented convention: index 0 for upper bounds (? extends) and index 1 for lower bounds (? super), as specified in the TypePathEntry javadoc and implemented consistently in CreateNestedAnnotationInfoVisitor.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (4)

28-28: LGTM: Import addition for Set.

Required for the Map<Integer, Set<NestedAnnotationInfo>> type used in MethodInfo.


53-59: LGTM: MethodInfo record updated with nested annotation tracking.

The nestedAnnotationsList field captures nested annotations for both return type (key -1) and parameters (key = parameter index). The Map<Integer, Set<NestedAnnotationInfo>> structure efficiently supports multiple nested annotations per position.


158-172: LGTM: Return type nested annotation collection.

The implementation correctly:

  1. Creates a fresh visitor per return type
  2. Guards collection with hasJSpecifyAnnotationDeep check
  3. Only adds to the map if nested annotations exist

The visitor pattern is applied consistently with the parameter handling below.


177-189: LGTM: Parameter nested annotation collection.

The implementation correctly iterates over all parameters unconditionally, ensuring nested annotations are collected regardless of whether the return type already set methodHasAnnotations. This addresses the bug flagged in previous reviews where parameters were skipped when the return type had annotations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java:
- Around line 116-120: The visitType method in CreateNestedAnnotationInfoVisitor
currently returns null; change it to return an empty set for consistency with
other visit methods (e.g., return Collections.emptySet() or an immutable empty
Set) so callers don't need to special-case null; update the import if needed and
keep the @Nullable signature intact while returning Collections.emptySet()
instead of null.
- Around line 23-24: In CreateNestedAnnotationInfoVisitor, change the instance
fields "path" (ArrayDeque<TypePathEntry>) and "nestedAnnotationInfoList"
(Set<NestedAnnotationInfo>) from package-private to private to encapsulate
state; update any external accesses to use new accessors or move usage into the
class (add private getters only if needed) and ensure constructor/visitor
methods inside CreateNestedAnnotationInfoVisitor still reference these fields
directly.

In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:
- Around line 180-192: The current guard "if (!methodHasAnnotations)" prevents
scanning parameters when the return type already set methodHasAnnotations, so
nested parameter annotations are skipped; inside the method handling for
Symbol.MethodSymbol mSym, always iterate over mSym.getParameters() and run
hasJSpecifyAnnotationDeep and vSym.asType().accept(new
CreateNestedAnnotationInfoVisitor(), null) for each parameter, setting
methodHasAnnotations when any annotation is found and populating
nestedAnnotationsMap.put(idx, nested) when nested is non-null/non-empty; remove
or move the early guard so parameter scanning runs unconditionally while
preserving the existing logic that sets methodHasAnnotations and fills
nestedAnnotationsMap.
- Around line 159-162: Remove the dead/commented code and the no-op null check:
delete the commented ImmutableSetMultimap.Builder line and the empty conditional
that checks nestedAnnotationsMap for null; keep the valid initialization
Map<Integer, Set<NestedAnnotationInfo>> nestedAnnotationsMap = new HashMap<>();
and ensure no other references depend on the removed lines in
NullnessAnnotationSerializer (search for nestedAnnotationsBuilder or the no-op
if block to confirm).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4d1e8 and c2fb5ff.

📒 Files selected for processing (4)
  • jdk-javac-plugin/build.gradle
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/build.gradle
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-11-25T22:43:06.446Z
Learnt from: CR
Repo: uber/NullAway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T22:43:06.446Z
Learning: Run only the tests for the main NullAway module using `./gradlew :nullaway:test` unless specifically asked to run tests in a different module

Applied to files:

  • jdk-javac-plugin/build.gradle
🧬 Code graph analysis (1)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (1)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (1)
  • NullMarked (43-333)
🔇 Additional comments (5)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (1)

404-512: Good test coverage for nested annotations.

The nestedAnnotationsForMethods() test provides thorough coverage of:

  • Nested annotations in type arguments (Class<@NonNull E>)
  • Return type nested annotations (List<@Nullable String>)
  • Array annotations (String @Nullable [], List<@NonNull String>[])
  • Wildcard bounds (? extends @Nullable String)

This validates the core functionality of the CreateNestedAnnotationInfoVisitor.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (3)

56-73: Verify array annotation handling semantics.

For String @Nullable [], the annotation is on the array type itself. The code adds an ARRAY_ELEMENT path entry and then checks annotations on the ArrayType t, not on t.elemtype.

Per Java's type annotation semantics, String @Nullable [] has the annotation on the array dimension. However, the path entry ARRAY_ELEMENT typically means "navigate into the element type." Please verify this matches the expected TypePath semantics from java.lang.reflect.AnnotatedType or JVM bytecode type paths.


31-54: LGTM: visitClassType implementation is correct.

The method properly:

  1. Iterates through type arguments with correct indexing
  2. Maintains the path stack with push/pop semantics
  3. Checks for both @Nullable and @NonNull annotations
  4. Creates immutable path snapshots before recording
  5. Recursively visits nested type structures

75-114: LGTM: Wildcard handling correctly distinguishes upper and lower bounds.

The implementation correctly uses:

  • WILDCARD_BOUND, 0 for extends bounds (? extends T)
  • WILDCARD_BOUND, 1 for super bounds (? super T)

This matches the JVM TypePath encoding where 0 indicates an upper bound and 1 indicates a lower bound for wildcards.

jdk-javac-plugin/build.gradle (1)

60-61: Dependency structure is safe—no circular dependency risk.

Adding project(':nullaway') as an implementation dependency introduces coupling between jdk-javac-plugin and the main nullaway module, which is necessary to share NestedAnnotationInfo. The nullaway module has no dependency on jdk-javac-plugin, so there is no circular dependency concern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2fb5ff and 4c9211b.

📒 Files selected for processing (1)
  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
🔇 Additional comments (1)
jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java (1)

3-3: LGTM!

The new imports are necessary to support the custom Gson type adapter for ImmutableList deserialization.

Also applies to: 7-8, 19-19

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:
- Around line 161-170: Return-type nested annotation collection is done
unconditionally while parameter nested collection is gated by
hasJSpecifyAnnotationDeep; make them consistent by always invoking the
CreateNestedAnnotationInfoVisitor for parameter types (like you do for
mSym.getReturnType()) and inserting any non-empty Set<NestedAnnotationInfo> into
nestedAnnotationsMap keyed by the parameter index. Keep the existing
hasJSpecifyAnnotationDeep checks solely to set methodHasAnnotations (do not use
it to skip running paramType.accept), and use the same pattern/symbols used for
the return type: CreateNestedAnnotationInfoVisitor, nestedAnnotationsMap,
methodHasAnnotations, and the parameter type accessors (the param symbol/type
used where mSym.getReturnType() is used).

In
@jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java:
- Around line 519-535: The deserializer assigned to
JsonDeserializer<ImmutableList<?>> (immutableListAdapter) unsafely casts typeOfT
to ParameterizedType and assumes json.isJsonArray() is true; update it to first
check that typeOfT is an instance of ParameterizedType before extracting actual
type arguments (handle raw ImmutableList by returning empty list or throwing a
clear JsonParseException), and defensively handle json being null or JsonNull
and non-array JsonElements by either returning an empty ImmutableList or
throwing a descriptive JsonParseException; ensure you still use
context.deserialize(element, contentClass) when contentClass is available.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c9211b and e7e19be.

📒 Files selected for processing (2)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
🔇 Additional comments (8)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (3)

17-17: LGTM on imports and record update.

The new imports for NestedAnnotationInfo and Set are appropriate, and the MethodInfo record extension to include nestedAnnotationsList follows a clean design pattern with -1 for return type and 0, 1, ... for parameter indices.

Also applies to: 29-29, 54-60


176-186: LGTM on parameter nested annotation collection.

The index-based iteration correctly maps parameter positions to their nested annotations, and the guard against null/empty sets prevents unnecessary map entries.


194-206: LGTM on MethodInfo construction and addition logic.

The nestedAnnotationsMap is correctly passed to the MethodInfo constructor, and the existing filtering logic ensures only methods with JSpecify annotations are added.

jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (5)

6-6: LGTM on new imports.

All new imports are necessary for the ImmutableList deserializer and nested annotation test assertions.

Also applies to: 12-14, 18-20, 22-23, 29-30, 33-33


122-123: LGTM on existing test updates.

The addition of new HashMap<>() to existing test expectations correctly accommodates the new nestedAnnotationsList field in MethodInfo.

Also applies to: 157-158, 216-217, 253-254, 297-298, 304-305, 311-312


326-326: LGTM on annotationDeepScan test updates.

The method name fix (wildCardwildcard) and the nested annotation assertions are well-structured. The WILDCARD_BOUND index encoding (0 for extends, 1 for super) is applied consistently.

Also applies to: 350-401


404-513: LGTM on new nestedAnnotationsForMethods test.

Excellent test coverage for the nested annotation feature, including:

  • Type argument annotations (Class<@NonNull E>)
  • Return type nested annotations (List<@Nullable String>)
  • Array element annotations (String @Nullable [])
  • Combined array + type argument paths (List<@NonNull String>[])
  • Wildcard extends bounds (? extends @Nullable String)

The test assertions correctly validate the type path construction for each scenario.


545-550: LGTM on Gson adapter registration.

The ImmutableList adapter is correctly registered with the GsonBuilder to enable proper deserialization of nested annotation type paths.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java:
- Around line 23-29: Make the visitor's state immutable and avoid exposing
mutable internals: mark the fields path and nestedAnnotationInfoList as final
and rename nestedAnnotationInfoList → nestedAnnotationInfoSet for clarity; keep
path private/final (ArrayDeque<TypePathEntry>) and do not expose it. Wherever
the visitor currently returns its set (e.g., a getNestedAnnotationInfo or the
top-level visit method), return an immutable copy (e.g.,
Set.copyOf(nestedAnnotationInfoSet) or Collections.unmodifiableSet(new
HashSet<>(nestedAnnotationInfoSet))) instead of the internal collection so
callers cannot mutate internal state.
- Around line 122-148: The two methods hasNullableAnnotation and
hasNonNullAnnotation are duplicated; replace them with a single helper, e.g.,
hasAnnotation(TypeMirror type, String qualifiedName) (or
hasAnnotationByQualifiedName) that encapsulates the null-check and loop over
type.getAnnotationMirrors(), and then rewrite hasNullableAnnotation and
hasNonNullAnnotation to call that helper (or remove them and inline calls) using
constants for the two annotation names (e.g., JSPECIFY_NULLABLE =
"org.jspecify.annotations.Nullable" and JSPECIFY_NONNULL =
"org.jspecify.annotations.NonNull") to avoid repeated string literals; update
all usages in CreateNestedAnnotationInfoVisitor to use the new helper or
constants.
- Around line 75-114: In visitWildcardType in CreateNestedAnnotationInfoVisitor
the local variable nestedAnnotationInfo is reused across the upper and lower
bound blocks causing a stale value from the extends-bound to be added again for
an unannotated super-bound; fix by scoping/resetting nestedAnnotationInfo per
bound (e.g., declare a new nestedAnnotationInfo inside the if
(t.getExtendsBound() != null) block and again inside the if (t.getSuperBound()
!= null) block or explicitly set it to null before computing the lower-bound) so
only annotations found on the current bound produce a NestedAnnotationInfo added
to nestedAnnotationInfoList.
- Around line 31-54: Add a clarifying comment inside the visitClassType method
(in CreateNestedAnnotationInfoVisitor) stating that this method only processes
classType.getTypeArguments() and does not traverse or handle annotations on
enclosing/inner types (classType.getEnclosingType()), because the
TypePathEntry.Kind enum currently only includes ARRAY_ELEMENT, TYPE_ARGUMENT,
and WILDCARD_BOUND and does not represent inner-type positions; e.g., "Note:
does not yet handle annotations on enclosing types, e.g., Outer<@Nullable
T>.Inner".
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7e19be and b21c4d2.

📒 Files selected for processing (1)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🔇 Additional comments (2)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (2)

116-120: Code state does not match review comment—visitType already returns the accumulated set, not null.

The actual implementation at line 118 already returns nestedAnnotationInfoList (the accumulated set), not null. All visitor methods (visitClassType, visitArrayType, visitWildcardType, visitType) follow the same pattern of accumulating results and returning the shared set. No changes are needed.

The concern about wrapper types (AnnotatedType, ForAll, Union, Intersection) cannot be verified as these types do not appear to be used in the codebase.

Likely an incorrect or invalid review comment.


56-73: The code correctly handles both array reference and array element annotations. Tests confirm that javac places @Nullable String[] annotations on the element type (String), not the ArrayType node, so they are found during the recursive elemtype traversal, not the initial hasNullableAnnotation(t) check. The String @Nullable [] syntax (annotation on array itself) correctly records as nested with ARRAY_ELEMENT path. No fix required.

Likely an incorrect or invalid review comment.

Comment on lines 23 to 29
private ArrayDeque<TypePathEntry> path;
private Set<NestedAnnotationInfo> nestedAnnotationInfoList;

public CreateNestedAnnotationInfoVisitor() {
path = new ArrayDeque<>();
nestedAnnotationInfoList = new HashSet<>();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Make visitor state safer (final fields + clearer naming + avoid exposing mutable internals).

path/nestedAnnotationInfoList are mutable instance state and the returned Set is the same object (callers can mutate it). At minimum: mark fields final, rename nestedAnnotationInfoListnestedAnnotationInfoSet, and consider returning an immutable copy at the end of the top-level call.

Proposed diff
-  private ArrayDeque<TypePathEntry> path;
-  private Set<NestedAnnotationInfo> nestedAnnotationInfoList;
+  private final ArrayDeque<TypePathEntry> path = new ArrayDeque<>();
+  private final Set<NestedAnnotationInfo> nestedAnnotationInfoSet = new HashSet<>();

   public CreateNestedAnnotationInfoVisitor() {
-    path = new ArrayDeque<>();
-    nestedAnnotationInfoList = new HashSet<>();
   }
🤖 Prompt for AI Agents
In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
around lines 23 - 29, Make the visitor's state immutable and avoid exposing
mutable internals: mark the fields path and nestedAnnotationInfoList as final
and rename nestedAnnotationInfoList → nestedAnnotationInfoSet for clarity; keep
path private/final (ArrayDeque<TypePathEntry>) and do not expose it. Wherever
the visitor currently returns its set (e.g., a getNestedAnnotationInfo or the
top-level visit method), return an immutable copy (e.g.,
Set.copyOf(nestedAnnotationInfoSet) or Collections.unmodifiableSet(new
HashSet<>(nestedAnnotationInfoSet))) instead of the internal collection so
callers cannot mutate internal state.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java:
- Around line 121-147: Remove the unreachable null checks in
hasNullableAnnotation(TypeMirror) and hasNonNullAnnotation(TypeMirror): delete
the "if (type == null) { return false; }" guards at the top of both methods
since TypeMirror is non-null under @NullMarked, leaving the existing iteration
over type.getAnnotationMirrors() and returns unchanged.
- Around line 76-114: The variable nestedAnnotationInfo is declared once at the
top of visitWildcardType and then reused for both the extends and super bound
logic; make nestedAnnotationInfo a locally-scoped variable inside each bound
block (the extends block handling t.getExtendsBound() and the super block
handling t.getSuperBound()) by removing the outer declaration and declaring a
new NestedAnnotationInfo nestedAnnotationInfo = null inside each block before
you check hasNullableAnnotation/hasNonNullAnnotation, leaving the rest of the
logic (adding to nestedAnnotationInfoList, calling accept, and removing path
entries) unchanged.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b21c4d2 and 0d0f0de.

📒 Files selected for processing (1)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🔇 Additional comments (4)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (4)

117-119: LGTM!

The default visitType implementation correctly returns the accumulated results for types that don't need special traversal logic.


149-151: LGTM!

The getTypePath helper correctly creates an immutable snapshot of the current path for inclusion in NestedAnnotationInfo instances.


23-29: No action needed. The visitor is instantiated with new CreateNestedAnnotationInfoVisitor() at each call site in NullnessAnnotationSerializer.java, ensuring fresh state for each type traversal. State accumulation is not a concern, and a reset method is unnecessary.

Likely an incorrect or invalid review comment.


57-73: No issues identified. The logic for handling array type annotations is correct.

The code properly distinguishes between:

  • String @Nullable [] – annotation on the array dimension, captured with path [ARRAY_ELEMENT]
  • @Nullable String[] – annotation on the element type, handled by recursive visitation of elemtype

Checking annotations on the ArrayType object correctly identifies annotations on the array dimension itself (e.g., String @Nullable []), and the subsequent recursive visitation of t.elemtype handles annotations within the element type (e.g., List<@Nullable String>). The path management is semantically sound.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java:
- Around line 518-537: The adapter immutableListAdapter currently throws on a
raw ImmutableList Type; change it to mirror AstubxGenerator by treating
non-parameterized ImmutableList as List<Object> instead of throwing: when
typeOfT is not a ParameterizedType, set the element/content type to Object (or a
generic Object Type) and proceed to deserialize each JsonElement with that
fallback type, then build the ImmutableList; keep the existing path for
ParameterizedType to use paramType.getActualTypeArguments()[0].
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d0f0de and d83bc26.

📒 Files selected for processing (6)
  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
  • jdk-javac-plugin/build.gradle
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
🔇 Additional comments (10)
jdk-javac-plugin/build.gradle (1)

60-60: LGTM!

The Guava dependency is appropriately added to support ImmutableList usage in the new NestedAnnotationInfo and CreateNestedAnnotationInfoVisitor classes.

jdk-annotations/astubx-generator/src/main/java/com/uber/nullaway/jdkannotations/AstubxGenerator.java (1)

171-199: LGTM!

The ImmutableList type adapter is well-implemented, correctly handling null JSON values, raw types, and parameterized types. This is necessary since Gson doesn't natively support Guava's immutable collections.

jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (1)

403-511: Good test coverage for nested annotations.

The new nestedAnnotationsForMethods() test comprehensively covers multiple scenarios:

  • Type argument annotations with @NonNull
  • Return type nested annotations (index -1)
  • Parameter array annotations with nested List<@NonNull String>[]
  • Wildcard upper bound annotations

The wildcard indices correctly use 0 for extends and 1 for super per the TypePathEntry documentation.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NestedAnnotationInfo.java (1)

1-42: Well-designed data model.

The record-based design is clean and appropriately immutable. The Javadoc clearly documents the semantics of the index field for each Kind, which is essential for consumers of this API.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (2)

158-186: LGTM!

The nested annotation collection logic is well-structured:

  • Return type annotations stored at key -1
  • Parameter annotations stored at their respective indices
  • A new CreateNestedAnnotationInfoVisitor instance is correctly created for each type to avoid state mixing

53-59: LGTM!

The MethodInfo record is appropriately extended with the nestedAnnotationsList field to carry nested annotation metadata for both return types and parameters.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (4)

33-57: LGTM!

The visitClassType method correctly traverses type arguments, maintaining the path stack and recording annotations with their corresponding type paths. The recursive call on line 52 ensures nested generic types are also processed.


78-118: LGTM!

The wildcard handling correctly distinguishes between upper bounds (extends, index 0) and lower bounds (super, index 1), matching the documented semantics in TypePathEntry. Both bounds are processed with appropriate path tracking.


125-137: Defensive null check is appropriate.

The null check on line 126 is defensive since TypeMirror parameters from bound accessors (like getExtendsBound()) can potentially be null. The hasAnnotation method is static and correctly handles this edge case.


59-76: The implementation is correct as-is. The design intentionally separates annotation tracking based on position:

  • Annotations on the element type like @Nullable String[] are captured in the serialized type string ([email protected] String[]) via toString() and do not require path-based NestedAnnotationInfo entries.
  • Annotations on the array type itself like String @Nullable [] are captured with path-based tracking (NestedAnnotationInfo with ARRAY_ELEMENT path entry).

This is consistent with JVMS attribute encoding: nested annotations on base types don't need positional tracking since they're part of the canonical type representation. The visitor correctly recurses into elemtype but only records path-based info for structural positions (array, wildcard, type argument).

Comment on lines +518 to +537
JsonDeserializer<ImmutableList<?>> immutableListAdapter =
(json, typeOfT, context) -> {
ImmutableList.Builder<Object> builder = ImmutableList.builder();

if (json.isJsonArray()) {
JsonArray array = json.getAsJsonArray();
// Get the component type of list (with defensive check)
if (!(typeOfT instanceof ParameterizedType paramType)) {
throw new IllegalArgumentException("Expected parameterized ImmutableList type");
}
Type contentClass = paramType.getActualTypeArguments()[0];

for (JsonElement element : array) {
// Deserialize the inner element using Gson's context
Object value = context.deserialize(element, contentClass);
builder.add(value);
}
}
return builder.build();
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider aligning the adapter behavior with AstubxGenerator.

This adapter throws an exception for non-parameterized ImmutableList types, while the adapter in AstubxGenerator gracefully handles this case by deserializing as List<Object>. While tests may never encounter raw ImmutableList types, aligning the behavior would improve consistency across the codebase.

♻️ Suggested alignment
         (json, typeOfT, context) -> {
           ImmutableList.Builder<Object> builder = ImmutableList.builder();

           if (json.isJsonArray()) {
             JsonArray array = json.getAsJsonArray();
             // Get the component type of list (with defensive check)
             if (!(typeOfT instanceof ParameterizedType paramType)) {
-              throw new IllegalArgumentException("Expected parameterized ImmutableList type");
+              // Raw ImmutableList, deserialize as List<Object>
+              for (JsonElement element : array) {
+                Object value = context.deserialize(element, Object.class);
+                builder.add(value);
+              }
+              return builder.build();
             }
             Type contentClass = paramType.getActualTypeArguments()[0];

             for (JsonElement element : array) {
               // Deserialize the inner element using Gson's context
               Object value = context.deserialize(element, contentClass);
               builder.add(value);
             }
           }
           return builder.build();
         };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
JsonDeserializer<ImmutableList<?>> immutableListAdapter =
(json, typeOfT, context) -> {
ImmutableList.Builder<Object> builder = ImmutableList.builder();
if (json.isJsonArray()) {
JsonArray array = json.getAsJsonArray();
// Get the component type of list (with defensive check)
if (!(typeOfT instanceof ParameterizedType paramType)) {
throw new IllegalArgumentException("Expected parameterized ImmutableList type");
}
Type contentClass = paramType.getActualTypeArguments()[0];
for (JsonElement element : array) {
// Deserialize the inner element using Gson's context
Object value = context.deserialize(element, contentClass);
builder.add(value);
}
}
return builder.build();
};
JsonDeserializer<ImmutableList<?>> immutableListAdapter =
(json, typeOfT, context) -> {
ImmutableList.Builder<Object> builder = ImmutableList.builder();
if (json.isJsonArray()) {
JsonArray array = json.getAsJsonArray();
// Get the component type of list (with defensive check)
if (!(typeOfT instanceof ParameterizedType paramType)) {
// Raw ImmutableList, deserialize as List<Object>
for (JsonElement element : array) {
Object value = context.deserialize(element, Object.class);
builder.add(value);
}
return builder.build();
}
Type contentClass = paramType.getActualTypeArguments()[0];
for (JsonElement element : array) {
// Deserialize the inner element using Gson's context
Object value = context.deserialize(element, contentClass);
builder.add(value);
}
}
return builder.build();
};
🤖 Prompt for AI Agents
In
@jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
around lines 518 - 537, The adapter immutableListAdapter currently throws on a
raw ImmutableList Type; change it to mirror AstubxGenerator by treating
non-parameterized ImmutableList as List<Object> instead of throwing: when
typeOfT is not a ParameterizedType, set the element/content type to Object (or a
generic Object Type) and proceed to deserialize each JsonElement with that
fallback type, then build the ImmutableList; keep the existing path for
ParameterizedType to use paramType.getActualTypeArguments()[0].

@codecov
Copy link

codecov bot commented Jan 11, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.37%. Comparing base (1ca1b25) to head (c558007).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../uber/nullaway/jdkannotations/AstubxGenerator.java 50.00% 4 Missing and 4 partials ⚠️
...javacplugin/CreateNestedAnnotationInfoVisitor.java 96.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1432   +/-   ##
=========================================
  Coverage     88.36%   88.37%           
- Complexity     2681     2702   +21     
=========================================
  Files            97       99    +2     
  Lines          8882     8965   +83     
  Branches       1779     1790   +11     
=========================================
+ Hits           7849     7923   +74     
- Misses          511      516    +5     
- Partials        522      526    +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks promising! Some comments:

for (int idx = 0; idx < typeArguments.size(); idx++) {
path.addLast(new TypePathEntry(TypePathEntry.Kind.TYPE_ARGUMENT, idx));
Type typeArg = typeArguments.get(idx);
ImmutableList<TypePathEntry> typePath = getTypePath();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not store this in a local variable here, since it will do a copy of path for every type variable, whether it is annotated or not. Instead, just call getTypePath() at lines 45 and 47


@NullMarked
public class CreateNestedAnnotationInfoVisitor
extends Types.DefaultTypeVisitor<Set<NestedAnnotationInfo>, @Nullable Void> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're storing the nestedAnnotationInfoSet in a field, I don't think we need to return it from every visitor method. Instead, make this:

Suggested change
extends Types.DefaultTypeVisitor<Set<NestedAnnotationInfo>, @Nullable Void> {
extends Types.DefaultTypeVisitor<@Nullable Void, @Nullable Void> {

Then, return null from every visitXXX method, but add a getter method getNestedAnnotationInfoSet() so that the client can retrieve the set at the end.

new HashMap<>(
Map.of(
0,
new HashSet<>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this? The Set.of result should be enough?

new HashMap<>(
Map.of(
0,
new HashSet<>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I don't think this is needed

false,
false,
List.of(new TypeParamInfo("E", List.of("@Nullable Object"))),
new HashMap<>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we shouldn't need to ever do new HashMap<>(Map.of(...)) or new HashSet<>(Set.of(...)) in this test code I think...

Comment on lines 474 to 481
new HashSet<>(
Set.of(
new NestedAnnotationInfo(
Annotation.NULLABLE,
ImmutableList.of(
new TypePathEntry(
TypePathEntry.Kind.ARRAY_ELEMENT,
-1))))),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is wrong! If the array element type was @Nullable, that would be written as @Nullable String [], not String @Nullable []. See here. Please fix.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java`:
- Around line 42-47: Remove the stray closing parenthesis and eliminate the
unused local by using the computed typePath variable: replace calls to
getTypePath() inside the nestedAnnotationInfoSet.add(...) invocations with the
previously declared ImmutableList<TypePathEntry> typePath, and remove the extra
')' after new NestedAnnotationInfo(Annotation.NONNULL, getTypePath()) so both
branches add new NestedAnnotationInfo(Annotation.NULLABLE, typePath) or new
NestedAnnotationInfo(Annotation.NONNULL, typePath); keep existing checks
hasNullableAnnotation(typeArg), hasNonNullAnnotation(typeArg), and the
nestedAnnotationInfoSet as-is.
♻️ Duplicate comments (3)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (3)

18-20: Add class-level Javadoc.

The class is missing documentation explaining its purpose and usage. Per past review feedback, add Javadoc describing that this visitor traverses Java type structures to collect nested JSpecify nullability annotations into a Set<NestedAnnotationInfo>.


55-72: Simplify to match the pattern suggested for visitClassType.

Per past review feedback, this method can be simplified by removing the intermediate nestedAnnotationInfo variable and directly adding to the set when an annotation is found.

♻️ Proposed simplification
   public `@Nullable` Set<NestedAnnotationInfo> visitArrayType(
       Type.ArrayType t, `@Nullable` Void unused) {
     path.addLast(new TypePathEntry(TypePathEntry.Kind.ARRAY_ELEMENT, -1));
-    ImmutableList<TypePathEntry> typePath = getTypePath();
-    NestedAnnotationInfo nestedAnnotationInfo = null;
     if (hasNullableAnnotation(t)) {
-      nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NULLABLE, typePath);
+      nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NULLABLE, getTypePath()));
     } else if (hasNonNullAnnotation(t)) {
-      nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NONNULL, typePath);
-    }
-    if (nestedAnnotationInfo != null) {
-      nestedAnnotationInfoSet.add(nestedAnnotationInfo);
+      nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath()));
     }
     t.elemtype.accept(this, null);
     path.removeLast();
     return nestedAnnotationInfoSet;
   }

74-114: Extract repeated annotation-checking logic to a helper method.

The pattern of checking for Nullable/NonNull annotations and adding to nestedAnnotationInfoSet is repeated multiple times (in visitClassType, visitArrayType, and twice here in visitWildcardType). Per past review feedback, extract this to a helper method.

♻️ Proposed helper method and refactored visitWildcardType

Add a helper method:

private void addNestedAnnotationIfPresent(TypeMirror type) {
  if (hasNullableAnnotation(type)) {
    nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NULLABLE, getTypePath()));
  } else if (hasNonNullAnnotation(type)) {
    nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath()));
  }
}

Then simplify visitWildcardType:

   public `@Nullable` Set<NestedAnnotationInfo> visitWildcardType(
       Type.WildcardType t, `@Nullable` Void unused) {
     // Upper Bound (? extends T)
     if (t.getExtendsBound() != null) {
-      NestedAnnotationInfo nestedAnnotationInfo = null;
       path.addLast(new TypePathEntry(TypePathEntry.Kind.WILDCARD_BOUND, 0));
-      ImmutableList<TypePathEntry> typePath = getTypePath();
-      Type upperBound = t.getExtendsBound();
-      if (hasNullableAnnotation(upperBound)) {
-        nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NULLABLE, typePath);
-      } else if (hasNonNullAnnotation(upperBound)) {
-        nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NONNULL, typePath);
-      }
-      if (nestedAnnotationInfo != null) {
-        nestedAnnotationInfoSet.add(nestedAnnotationInfo);
-      }
+      addNestedAnnotationIfPresent(t.getExtendsBound());
       t.getExtendsBound().accept(this, null);
       path.removeLast();
     }

     // Lower Bound (? super T)
     if (t.getSuperBound() != null) {
-      NestedAnnotationInfo nestedAnnotationInfo = null;
       path.addLast(new TypePathEntry(TypePathEntry.Kind.WILDCARD_BOUND, 1));
-      ImmutableList<TypePathEntry> typePath = getTypePath();
-      Type lowerBound = t.getSuperBound();
-      if (hasNullableAnnotation(lowerBound)) {
-        nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NULLABLE, typePath);
-      } else if (hasNonNullAnnotation(lowerBound)) {
-        nestedAnnotationInfo = new NestedAnnotationInfo(Annotation.NONNULL, typePath);
-      }
-      if (nestedAnnotationInfo != null) {
-        nestedAnnotationInfoSet.add(nestedAnnotationInfo);
-      }
+      addNestedAnnotationIfPresent(t.getSuperBound());
       t.getSuperBound().accept(this, null);
       path.removeLast();
     }

     return nestedAnnotationInfoSet;
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d83bc26 and d5cc811.

📒 Files selected for processing (1)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
🔇 Additional comments (3)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (3)

1-16: LGTM!

Imports are appropriate and all appear to be used within the class.


116-119: LGTM!

The fallback visitType correctly returns the accumulated set for types that don't need special handling.


121-145: LGTM!

The helper methods are well-structured. The consolidated hasAnnotation method with constants avoids code duplication, and getTypePath() correctly returns an immutable copy of the path.

The null check at line 122 is technically unreachable under @NullMarked, but since hasAnnotation is static it provides defensive coding if called from other contexts.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 42 to 47
ImmutableList<TypePathEntry> typePath = getTypePath();
if (hasNullableAnnotation(typeArg)) {
nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NULLABLE, getTypePath()));
} else if (hasNonNullAnnotation(typeArg)) {
nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath())));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Syntax error and unused variable.

Two issues in this block:

  1. Critical: Line 46 has an extra closing parenthesis that will cause a compilation error.
  2. Line 42 declares typePath which is never used (lines 44-46 call getTypePath() directly).
🐛 Proposed fix
-        ImmutableList<TypePathEntry> typePath = getTypePath();
         if (hasNullableAnnotation(typeArg)) {
           nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NULLABLE, getTypePath()));
         } else if (hasNonNullAnnotation(typeArg)) {
-          nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath())));
+          nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath()));
         }
🤖 Prompt for AI Agents
In
`@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java`
around lines 42 - 47, Remove the stray closing parenthesis and eliminate the
unused local by using the computed typePath variable: replace calls to
getTypePath() inside the nestedAnnotationInfoSet.add(...) invocations with the
previously declared ImmutableList<TypePathEntry> typePath, and remove the extra
')' after new NestedAnnotationInfo(Annotation.NONNULL, getTypePath()) so both
branches add new NestedAnnotationInfo(Annotation.NULLABLE, typePath) or new
NestedAnnotationInfo(Annotation.NONNULL, typePath); keep existing checks
hasNullableAnnotation(typeArg), hasNonNullAnnotation(typeArg), and the
nestedAnnotationInfoSet as-is.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java`:
- Around line 86-88: getNestedAnnotationInfoSet currently exposes the internal
mutable HashSet allowing external mutation; change the getter in
CreateNestedAnnotationInfoVisitor to return an unmodifiable view (e.g.,
Collections.unmodifiableSet(nestedAnnotationInfoSet) or
Set.copyOf(nestedAnnotationInfoSet)) so callers cannot modify the visitor's
internal state, keeping the field type and name (nestedAnnotationInfoSet,
getNestedAnnotationInfoSet) unchanged.
♻️ Duplicate comments (4)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (1)

496-515: Consider returning an empty list for non-parameterized types instead of throwing.

The adapter throws when typeOfT is not a ParameterizedType. While this may never occur in tests, gracefully handling it (similar to AstubxGenerator) would improve robustness.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (3)

33-47: Add a comment clarifying that enclosing types are not traversed.

As noted in a previous review, visitClassType only processes getTypeArguments() and does not handle annotations on enclosing types (e.g., Outer<@Nullable T>.Inner). A brief comment would clarify this intentional limitation.


98-110: Remove redundant null check.

The type parameter has type TypeMirror (non-null under @NullMarked), making the null check at lines 99-101 unreachable.

🧹 Proposed fix
   private static boolean hasAnnotation(TypeMirror type, String qname) {
-    if (type == null) {
-      return false;
-    }
     for (AnnotationMirror annotation : type.getAnnotationMirrors()) {

18-20: Add class-level Javadoc.

The class is missing documentation explaining its purpose. A brief Javadoc describing that this visitor collects nested nullability annotations from Java types would be helpful.

📝 Suggested Javadoc
 `@NullMarked`
+/**
+ * A type visitor that collects nested JSpecify nullability annotations ({`@code` `@Nullable`} and
+ * {`@code` `@NonNull`}) from complex Java types such as parameterized types, arrays, and wildcards.
+ * The collected annotations are paired with their type paths for later serialization.
+ */
 public class CreateNestedAnnotationInfoVisitor
     extends Types.DefaultTypeVisitor<@Nullable Void, `@Nullable` Void> {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5cc811 and 42b3f41.

📒 Files selected for processing (3)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🔇 Additional comments (9)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (2)

119-120: LGTM: MethodInfo expectations correctly updated with empty nestedAnnotationsList maps.

The test expectations have been properly updated to include Map.of() for the new nestedAnnotationsList field in MethodInfo. The non-empty map cases (lines 354-364, 385-392) correctly model nested annotations with appropriate TypePathEntry paths.

Also applies to: 154-155, 213-214, 250-251, 294-295, 301-302, 308-309, 347-348, 354-364, 370-371, 377-378, 384-392


395-490: LGTM: Comprehensive test coverage for nested annotations.

The nestedAnnotationsForMethods() test effectively validates nested annotation extraction across multiple scenarios:

  • Type argument annotations (Class<@NonNull E>)
  • Return type nested annotations (List<@Nullable String>)
  • Parameter type nested annotations with arrays (@Nullable String[], List<@NonNull String>[])
  • Wildcard upper bound annotations (? extends @nullable String)

The TypePathEntry constructions correctly model the type paths (e.g., ARRAY_ELEMENT with index -1, WILDCARD_BOUND with index 0 for extends bound).

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java (3)

49-56: LGTM: Array type handling.

The visitor correctly pushes an ARRAY_ELEMENT path entry, checks for annotations on the element type, recursively visits the element type, and pops the path entry.


58-79: LGTM: Wildcard type handling.

The visitor correctly handles both upper bounds (? extends T, index 0) and lower bounds (? super T, index 1) with separate path entries and recursive visits. The scoped handling addresses the stale variable issue from past reviews.


90-96: LGTM: Clean helper method for annotation collection.

The extracted addNestedAnnotationInfo method consolidates the annotation-check-and-add logic, improving readability and reducing duplication.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (4)

53-59: LGTM: MethodInfo record extended with nested annotations.

The nestedAnnotationsList field is appropriately typed as Map<Integer, Set<NestedAnnotationInfo>>, using -1 for return type and parameter indices for parameters. This aligns with standard JVM type annotation indexing conventions.


160-172: LGTM: Return type nested annotation collection.

The nested annotation collection for return types is correctly gated by hasJSpecifyAnnotationDeep() and uses key -1 in the map. The visitor pattern properly extracts nested annotations.


177-189: LGTM: Parameter nested annotation collection fixed.

The parameter scanning loop now runs unconditionally, correctly collecting nested annotations for all parameters regardless of whether the return type has annotations. This addresses the bug identified in previous reviews.


197-204: LGTM: MethodInfo construction includes nested annotations map.

The nestedAnnotationsMap is correctly passed to the MethodInfo record constructor, ensuring nested annotation data is serialized to JSON.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +86 to +88
public Set<NestedAnnotationInfo> getNestedAnnotationInfoSet() {
return nestedAnnotationInfoSet;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider returning an unmodifiable view of the set.

getNestedAnnotationInfoSet() returns the internal mutable HashSet, allowing callers to modify the visitor's state. Consider returning Collections.unmodifiableSet(nestedAnnotationInfoSet) or Set.copyOf(nestedAnnotationInfoSet).

🤖 Prompt for AI Agents
In
`@jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/CreateNestedAnnotationInfoVisitor.java`
around lines 86 - 88, getNestedAnnotationInfoSet currently exposes the internal
mutable HashSet allowing external mutation; change the getter in
CreateNestedAnnotationInfoVisitor to return an unmodifiable view (e.g.,
Collections.unmodifiableSet(nestedAnnotationInfoSet) or
Set.copyOf(nestedAnnotationInfoSet)) so callers cannot modify the visitor's
internal state, keeping the field type and name (nestedAnnotationInfoSet,
getNestedAnnotationInfoSet) unchanged.

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of nits

haewiful and others added 5 commits January 14, 2026 17:07
…reateNestedAnnotationInfoVisitor.java

Co-authored-by: Manu Sridharan <[email protected]>
…reateNestedAnnotationInfoVisitor.java

Co-authored-by: Manu Sridharan <[email protected]>
…ullnessAnnotationSerializer.java

Co-authored-by: Manu Sridharan <[email protected]>
…ullnessAnnotationSerializer.java

Co-authored-by: Manu Sridharan <[email protected]>
Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@msridhar msridhar enabled auto-merge (squash) January 15, 2026 18:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (1)

197-204: Consider making the nested annotations map immutable.

The nestedAnnotationsMap is passed directly to MethodInfo. While the map isn't modified after construction in the current code, wrapping with Map.copyOf() would provide defensive immutability, especially since the data is serialized to JSON and consumed externally.

♻️ Suggested defensive copy
                  MethodInfo methodInfo =
                      new MethodInfo(
                          returnType,
                          mSym.toString(),
                          hasNullMarked,
                          hasNullUnmarked,
                          methodTypeParams,
-                          nestedAnnotationsMap);
+                          Map.copyOf(nestedAnnotationsMap));
♻️ Duplicate comments (1)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (1)

513-532: ImmutableList adapter implementation is functional.

The adapter correctly deserializes parameterized ImmutableList types by extracting the component type and delegating to Gson's context. The exception for raw types is acceptable in test code where parameterized types are always expected.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e879332 and c558007.

📒 Files selected for processing (2)
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-09-24T23:14:37.777Z
Learnt from: haewiful
Repo: uber/NullAway PR: 1243
File: jdk-annotations/astubx-generator-cli/src/main/java/com/uber/nullaway/jdkannotations/AstubxGeneratorCLI.java:97-106
Timestamp: 2025-09-24T23:14:37.777Z
Learning: In the jdk-javac-plugin NullnessAnnotationSerializer, the plugin only creates one JSON file per compilation, so processing only the first JSON file is intentional behavior rather than a bug.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java
📚 Learning: 2025-09-03T19:40:11.118Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.118Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
📚 Learning: 2025-12-31T23:59:20.009Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1421
File: nullaway/src/main/java/com/uber/nullaway/NullAway.java:339-340
Timestamp: 2025-12-31T23:59:20.009Z
Learning: In Error Prone's ASTHelpers class, the getSymbol(MethodInvocationTree) overload is guaranteed to return a non-null Symbol.MethodSymbol. This differs from other overloads of getSymbol() that may return null. Therefore, no null check is required after calling ASTHelpers.getSymbol() with a MethodInvocationTree parameter.

Applied to files:

  • jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java
🔇 Additional comments (8)
jdk-javac-plugin/src/test/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializerTest.java (4)

6-30: LGTM: Import additions.

The new imports support the ImmutableList adapter (JsonDeserializer, JsonArray, JsonElement, ParameterizedType, Type), nested annotation types (NestedAnnotationInfo, TypePathEntry, Annotation), and Set for the nested annotation map values.


119-120: LGTM: Updated test expectations with empty nested annotation maps.

The Map.of() additions align with the updated MethodInfo record signature that now includes nestedAnnotationsList.


395-507: Comprehensive nested annotation test coverage.

This test method thoroughly covers:

  • Parameter nested annotations (@NonNull E in type argument)
  • Return type nested annotations (List<@Nullable String>)
  • Array element annotations (@Nullable String [])
  • Arrays of parameterized types (List<@NonNull String>[])
  • Wildcard upper bounds (? extends @nullable String)
  • Nested type arguments without array elements (List<List<String @nullable []>>)

The test cases address the scenarios requested in past review comments, including the List<List<String @nullable []>> case that should have two type arguments but no array elements in the type path.


355-364: No action required—the wildcard bound index convention is correct.

The test at line 364 correctly uses WILDCARD_BOUND index 1 for the super bound (? super @nullable Integer). This aligns with the documented convention: index 0 for upper bounds (? extends) and index 1 for lower bounds (? super), as specified in the TypePathEntry javadoc and implemented consistently in CreateNestedAnnotationInfoVisitor.

jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java (4)

28-28: LGTM: Import addition for Set.

Required for the Map<Integer, Set<NestedAnnotationInfo>> type used in MethodInfo.


53-59: LGTM: MethodInfo record updated with nested annotation tracking.

The nestedAnnotationsList field captures nested annotations for both return type (key -1) and parameters (key = parameter index). The Map<Integer, Set<NestedAnnotationInfo>> structure efficiently supports multiple nested annotations per position.


158-172: LGTM: Return type nested annotation collection.

The implementation correctly:

  1. Creates a fresh visitor per return type
  2. Guards collection with hasJSpecifyAnnotationDeep check
  3. Only adds to the map if nested annotations exist

The visitor pattern is applied consistently with the parameter handling below.


177-189: LGTM: Parameter nested annotation collection.

The implementation correctly iterates over all parameters unconditionally, ensuring nested annotations are collected regardless of whether the return type already set methodHasAnnotations. This addresses the bug flagged in previous reviews where parameters were skipped when the return type had annotations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@msridhar msridhar merged commit 32febbd into uber:master Jan 15, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants